package gnss

import (
	"dgo/goutils/utils"
	"fmt"
	"math"
	"strings"
	"sync/atomic"
	"time"
)

var (
	GnssYObs_create_cnt      int32 = 0
	GnssYObsEpoch_create_cnt int32 = 0
)

/**
 *
 */
type GnssYObsEpoch struct {
	Week uint32 // 如果为0需要使用引用时间
	TOW  uint32 // 周内毫秒

	IODS              byte
	ClkSteering       byte // 站钟调整策略标识 DF411 uint2 2   // Clock Steering NaviIndicator
	ExternalClk       byte // 外钟使用情况标识 DF412 uint2 2   // External Clock NaviIndicator
	SmoothingType     byte
	SmoothingInterval byte

	StaID uint16 // stationid

	obsMap [NAVI_SYS_MAX - 1][SATID_MAX]*GnssYObs
}

func NewGnssYObsEpoch() *GnssYObsEpoch {
	rval := &GnssYObsEpoch{}
	atomic.AddInt32(&GnssYObsEpoch_create_cnt, 1)
	return rval
}

func (this *GnssYObsEpoch) ResetRoughR() {
	this.Range(func(navisys byte, yobs *GnssYObs) bool {
		yobs.RoughR = 0
		return true
	})
}

func (this *GnssYObsEpoch) Reset() {
	this.Week = 0
	this.TOW = 0
	this.Range(func(navisys byte, yobs *GnssYObs) bool {
		yobs.Reset()
		return true
	})
}

func (this *GnssYObsEpoch) GetGpsTime(rt time.Time) time.Time {
	if this.Week > 0 {
		return TOW2TimeEx(uint(this.Week), uint(this.TOW))
	}
	if rt.IsZero() {
		return TOW2Time(time.Now().UTC(), uint(this.TOW))
	}

	return TOW2Time(rt, uint(this.TOW))
}

func (this *GnssYObsEpoch) StatNaviSatNum(navisys byte) int {
	obs := this.GetNaviObs(navisys)
	n := 0
	for i := 0; i < len(obs); i++ {
		sat := obs[i]
		if sat == nil || sat.Satid == 0 {
			n++
		}
	}
	return n
}

func (this *GnssYObsEpoch) StatSatNum() string {
	var sb utils.BytesBuilder
	for i := U8_0; i < NAVI_SYS_MAX-1; i++ {
		n := 0
		c2 := 0
		c3 := 0
		for j := U8_0; j < SATID_MAX; j++ {
			src := this.obsMap[i][j]
			if src != nil && src.Satid >= 0 {
				if src.NaviSys == SYS_BD {
					if src.Satid <= 16 {
						c2++
					} else {
						c3++
					}
				}
				n++
			}
		}
		if i+1 == SYS_BD {
			if c2 > 0 {
				sb.Appendf("%s:%d ", "C2", c2)
			}
			if c3 > 0 {
				sb.Appendf("%s:%d ", "C3", c3)
			}
			sb.Appendf("%s:%d ", GetNavSysCode(i+1), n)
		} else if n > 0 {
			sb.Appendf("%s:%d ", GetNavSysCode(i+1), n)
		}
	}
	return sb.String()
}

func (this *GnssYObsEpoch) StatSignalEx(cb func(navisys byte, singaltext string, singalstr string, signalid byte, flag byte)) {
	for i := U8_0; i < NAVI_SYS_MAX-1; i++ {
		var c2, c3, sigs string
		for j := U8_0; j < SATID_MAX; j++ {
			src := this.obsMap[i][j]
			if src != nil && src.Satid >= 0 {
				src.RangeSignal(func(signal *YObsSignal) bool {
					signalstr := signal.GetSignalCode()
					s := fmt.Sprintf("%s(%d)", signalstr, signal.SigId)
					if src.NaviSys == SYS_BD {
						if src.Satid <= 16 {
							if strings.Index(c2, s) == -1 {
								c2 += s + " "
								cb(src.NaviSys, s, signalstr, signal.SigId, 2)
							}
						} else {
							if strings.Index(c3, s) == -1 {
								c3 += s + " "
								cb(src.NaviSys, s, signalstr, signal.SigId, 3)
							}
						}
					}
					if strings.Index(sigs, s) == -1 {
						sigs += s + " "
						cb(src.NaviSys, s, signalstr, signal.SigId, 0)
					}
					return true
				})
			}
		}
	}
}

//
//func (this *GnssYObsEpoch) StatSignal_org() string {
//	var sb utils.BytesBuilder
//	for i := U8_0; i < NAVI_SYS_MAX-1; i++ {
//		var c2, c3, sigs string
//		for j := U8_0; j < SATID_MAX; j++ {
//			src := this.obsMap[i][j]
//			if src != nil && src.Satid >= 0 {
//				src.RangeSignal(func(signal *YObsSignal) bool {
//					s := fmt.Sprintf("%s(%d)", signal.GetSignalCode(), signal.SigId)
//					if src.NaviSys == SYS_BD {
//						if src.Satid <= 16 {
//							if strings.Index(c2, s) == -1 {
//								c2 += s + " "
//							}
//						} else {
//							if strings.Index(c3, s) == -1 {
//								c3 += s + " "
//							}
//						}
//					}
//					if strings.Index(sigs, s) == -1 {
//						sigs += s + " "
//					}
//					return true
//				})
//			}
//		}
//		if len(sigs) > 0 {
//			sb.Appendf("%s:%s ", GetNavSysCode(i+1), sigs)
//		}
//
//		if len(c2) > 0 {
//			sb.Appendf("C2:%s ", c2)
//		}
//		if len(c3) > 0 {
//			sb.Appendf("C3:%s ", c3)
//		}
//	}
//	return sb.String()
//}

func (this *GnssYObsEpoch) StatSignal() string {
	//var sb utils.BytesBuilder
	//this.StatSignalEx(func(navisys byte, singaltext string, singalstr string, signalid byte, flag byte) {
	//	if flag == 2 {
	//		sb.Appendf("C2:%s ", singaltext)
	//	} else if flag == 3 {
	//		sb.Appendf("C3:%s ", singaltext)
	//	} else {
	//		sb.Appendf("%s:%s ", GetNavSysCode(navisys), singaltext)
	//	}
	//})
	//return sb.String()

	var sb utils.BytesBuilder
	for i := U8_0; i < NAVI_SYS_MAX-1; i++ {
		var c2, c3, sigs string
		for j := U8_0; j < SATID_MAX; j++ {
			src := this.obsMap[i][j]
			if src != nil && src.Satid >= 0 {
				src.RangeSignal(func(signal *YObsSignal) bool {
					signalstr := signal.GetSignalCode()
					s := fmt.Sprintf("%s(%d)", signalstr, signal.SigId)
					if src.NaviSys == SYS_BD {
						if src.Satid <= 16 {
							if strings.Index(c2, s) == -1 {
								c2 += s + " "
							}
						} else {
							if strings.Index(c3, s) == -1 {
								c3 += s + " "
							}
						}
					}
					if strings.Index(sigs, s) == -1 {
						sigs += s + " "
					}
					return true
				})
			}
		}
		if len(c2) > 0 {
			sb.Appendf("C2:%s ", c2)
		}
		if len(c3) > 0 {
			sb.Appendf("C3:%s ", c3)
		}
		if len(sigs) > 0 {
			sb.Appendf("%s:%s ", GetNavSysCode(i+1), sigs)
		}

	}
	return sb.String()
}

func (this *GnssYObsEpoch) GetNaviObs(navisys byte) [SATID_MAX]*GnssYObs {
	return this.obsMap[navisys-1]
}

func (this *GnssYObsEpoch) CopyFrom(src *GnssYObsEpoch) {
	this.TOW = src.TOW
	this.Week = src.Week
	this.ExternalClk = src.ExternalClk
	this.ClkSteering = src.ClkSteering
	this.IODS = src.IODS
	this.SmoothingInterval = src.SmoothingInterval
	this.SmoothingType = src.SmoothingType
	this.StaID = src.StaID
	for i := U8_0; i < NAVI_SYS_MAX-1; i++ {
		for j := U8_0; j < SATID_MAX; j++ {
			src := src.obsMap[i][j]
			dst := this.obsMap[i][j]
			if src == nil || src.Satid == 0 {
				if dst != nil {
					dst.Reset()
				}
			} else {
				if dst == nil {
					dst = &GnssYObs{NaviSys: i + 1, Satid: j + 1}
					atomic.AddInt32(&GnssYObs_create_cnt, 1)
					this.obsMap[i][j] = dst
				}
				dst.CopyFrom(src)
			}
		}
	}
}

func (this *GnssYObsEpoch) Compare(slave *GnssYObsEpoch, outputfn func(msg string)) {
	if this.Week != slave.Week {
		outputfn(fmt.Sprintf("时间差异:Week(%d!=%d)", this.Week, slave.Week))
	}

	if this.TOW != slave.TOW {
		outputfn(fmt.Sprintf("时间差异:TOW(%d!=%d)", this.TOW, slave.TOW))
		return
	}

	var sb utils.BytesBuilder
	for i := U8_0; i < NAVI_SYS_MAX-1; i++ {
		for j := U8_0; j < SATID_MAX; j++ {
			om := this.obsMap[i][j]
			os := slave.obsMap[i][j]
			if om == nil || om.Satid == 0 {
				continue
			} else {
				if os == nil || os.Satid == 0 {
					outputfn(fmt.Sprintf("[%s]缺失卫星观测数据", om.GetPrnCode()))
				} else {
					var ss YObsSignal
					sb.Reset()
					sb.Appendf("[%s]观测值差异", om.GetPrnCode())
					n := 0
					om.RangeSignal(func(signal *YObsSignal) bool {
						ss.Reset()
						os.GetSignalBySigID(signal.SigId, &ss)

						if signal.IsValid() && !ss.IsValid() {
							sb.Appendf(",频点[%s]缺失C/L/S: %s%s %.2f", signal.GetSignalCode(), RinexFormatFloat(signal.PsV), RinexFormatFloat(signal.PhV), signal.Cnr)
							n++
							return true
						}

						psv := signal.PsV - ss.PsV
						if math.Abs(psv) > 0.003 {
							sb.Appendf(",C%s:%.4f-%.4f=%.4f", signal.GetSignalCode(), signal.PsV, ss.PsV, psv)
							n++
						}

						phv := signal.PhV - ss.PhV
						if math.Abs(phv) > 0.003 {
							sb.Appendf(",L%s: %.4f-%.4f=%.4f", signal.GetSignalCode(), signal.PhV, ss.PhV, phv)
							n++
						}

						cnr := signal.Cnr - ss.Cnr
						if math.Abs(float64(cnr)) > 0.003 {
							sb.Appendf(",S%s: %.2f-%.2f=%.2f", signal.GetSignalCode(), signal.Cnr, ss.Cnr, cnr)
							n++
						}

						if signal.LkT != ss.LkT {
							sb.Appendf(",%s(LkT) %d!=%d", signal.GetSignalCode(), signal.LkT, ss.LkT)
							n++
						}

						if signal.HalfAmb != ss.HalfAmb {
							sb.Appendf(",%s(HalfAmb) %d!=%d", signal.GetSignalCode(), signal.HalfAmb, ss.HalfAmb)
							n++
						}
						return true
					})
					if n > 0 {
						outputfn(sb.String())
					}
				}
			}
		}
	}
}

func (this *GnssYObsEpoch) CopySig(navisys, sigIdSrc, sigIdDest byte) {
	obslst := this.GetNaviObs(navisys)
	for satIdx := U8_0; satIdx < SATID_MAX; satIdx++ {
		obs := obslst[satIdx]
		if obs != nil && obs.Satid != 0 {
			obs.CopySig(sigIdSrc, sigIdDest)
		}
	}
}

func (this *GnssYObsEpoch) MoveSig(navisys, sigIdSrc, sigIdDest byte) {
	obslst := this.GetNaviObs(navisys)
	for satIdx := U8_0; satIdx < SATID_MAX; satIdx++ {
		obs := obslst[satIdx]
		if obs != nil && obs.Satid != 0 {
			obs.CopySig(sigIdSrc, sigIdDest)
			obs.EmptySig(sigIdSrc)
		}
	}
}

func (this *GnssYObsEpoch) SignalExists(navisys, sigID byte) bool {
	obslst := this.GetNaviObs(navisys)
	for satIdx := U8_0; satIdx < SATID_MAX; satIdx++ {
		obs := obslst[satIdx]
		if obs != nil && obs.Satid != 0 {
			if obs.IsSignalValidIdx(sigID - 1) {
				return true
			}
		}
	}
	return false
}

func (this *GnssYObsEpoch) PhaseCheckShift(navisys, sig byte, phaseshift float64) {
	obslst := this.GetNaviObs(navisys)
	for satIdx := U8_0; satIdx < SATID_MAX; satIdx++ {
		obs := obslst[satIdx]
		if obs != nil && obs.Satid != 0 {
			phv := obs.PhRngV[sig-1]
			if obs.IsSignalValidIdx(sig - 1) {
				obs.PhRngV[sig-1] = phv + phaseshift
			}
		}
	}
}

func (this *GnssYObsEpoch) RangeAll(cb func(navisys byte, satid byte, yobs *GnssYObs) bool) {
	for i := U8_0; i < NAVI_SYS_MAX-1; i++ {
		for j := U8_0; j < SATID_MAX; j++ {
			yobs := this.obsMap[i][j]
			if !cb(i+1, j+1, yobs) {
				break
			}
		}
	}
}

func (this *GnssYObsEpoch) Range(cb func(navisys byte, yobs *GnssYObs) bool) {
	for i := U8_0; i < NAVI_SYS_MAX-1; i++ {
		for j := U8_0; j < SATID_MAX; j++ {
			yobs := this.obsMap[i][j]
			if yobs != nil && yobs.Satid != 0 {
				if !cb(i+1, yobs) {
					break
				}
			}
		}
	}
}

func (this *GnssYObsEpoch) RangeSys(naviSys byte, cb func(obs *GnssYObs) bool) {
	for j := U8_0; j < SATID_MAX; j++ {
		yobs := this.obsMap[naviSys-1][j]
		if yobs != nil && yobs.Satid != 0 {
			if !cb(yobs) {
				break
			}
		}
	}
}

func (this *GnssYObsEpoch) GetSingal(naviSys byte, satid byte, sigcode string) (rval *YObsSignal) {
	if int(naviSys) > len(this.obsMap) || naviSys == 0 {
		return nil
	}
	yobs := this.obsMap[naviSys-1][satid-1]
	if yobs == nil {
		return nil
	}

	yobs.RangeSignalCopy(func(signal *YObsSignal) bool {
		if signal.GetSignalCode() == sigcode {
			rval = signal
		}
		return rval == nil
	})
	return
}

func (this *GnssYObsEpoch) GetRawObs(naviSys byte, satid byte) *GnssYObs {
	if int(naviSys) > len(this.obsMap) || naviSys == 0 || satid == 0 {
		return nil
	}
	return this.obsMap[naviSys-1][satid-1]
}

func (this *GnssYObsEpoch) SetRoughR(naviSys, satId byte, v float64) {
	yobs := this.CheckGetRawObs(naviSys, satId)
	yobs.RoughR = v
}

func (this *GnssYObsEpoch) SetObsSignal(osig *YObsSignal) {
	if osig.SatId == 0 {
		return
	}
	if osig.SatId > SATID_MAX {
		return
	}

	yobs := this.CheckGetRawObs(osig.NaviSys, osig.SatId)
	yobs.SetSignal(osig)
}

func (this *GnssYObsEpoch) DelNaviSys(naviSys byte) {
	if int(naviSys) > len(this.obsMap) || naviSys == 0 {
		return
	}

	for i := U8_0; i < SATID_MAX; i++ {
		v := this.obsMap[naviSys-1][i]
		if v != nil {
			v.Reset()
		}

	}
}

func (this *GnssYObsEpoch) DelSats(naviSys byte, startPrn, endPrn byte) {
	if int(naviSys) > len(this.obsMap) || naviSys == 0 {
		return
	}

	startIdx := U8_0
	endIdx := SATID_MAX - 1

	if startPrn > 0 {
		startIdx = startPrn - 1
	}

	if endPrn > 0 {
		endIdx = endPrn - 1
	}

	for i := startIdx; i <= endIdx; i++ {
		v := this.obsMap[naviSys-1][i]
		if v != nil {
			v.Reset()
		}
	}

}

func (this *GnssYObsEpoch) CheckGetRawObs(naviSys byte, satid byte) *GnssYObs {
	obs := this.GetRawObs(naviSys, satid)
	if obs == nil {
		obs = &GnssYObs{NaviSys: naviSys, Satid: satid}
		atomic.AddInt32(&GnssYObs_create_cnt, 1)
		this.obsMap[naviSys-1][satid-1] = obs
	} else {
		obs.NaviSys = naviSys
		obs.Satid = satid
	}
	return obs
}

func (this *GnssYObsEpoch) String() string {
	var bb utils.BytesBuilder
	for i := U8_0; i < NAVI_SYS_MAX-1; i++ {
		for j := U8_0; j < SATID_MAX; j++ {
			satObs := this.obsMap[i][j]
			if satObs != nil {
				satObs.Write(&bb)
			}
		}
	}
	return bb.String()
}
